/**
  ******************************************************************************
  * @file    spi_sdcard.c
  * @author  YANDLD
  * @version V3.0
  * @date    2015.05.29
  * @brief   www.beyondcore.net   http://upcmcu.taobao.com
  ******************************************************************************
  */

#include <string.h>
#include <stdbool.h>
#include "spi_sdcard.h"

#define SDCARD_DEBUG		0
#if ( SDCARD_DEBUG == 1 )
#include <stdio.h>
#define SDCARD_TRACE	printf
#else
#define SDCARD_TRACE(...)
#endif


#define SDC_CMD_RESPONSE_TRY_LOOPS  (200)
#define SDC_CMD_BUSY_TRY_LOOPS      (0xFFFF)

#define CMD0            (0)
#define CMD8            (8)
#define CMD9            (9)
#define CMD18           (18)
#define CMD12           (12)
#define CMD17           (17)
#define CMD24           (24)
#define CMD23           (23)
#define CMD25           (25)
#define CMD41           (41)
#define CMD58           (58)
#define CMD55           (55)

#if !defined(CH_OK)
#define CH_OK       (0)
#endif

#if !defined(CH_ERR)
#define CH_ERR      (1)
#endif

struct sdcard_device
{
    uint32_t type;
    uint8_t csd[16];
    uint32_t blk_cnt;
    struct sdcard_init_t    ops;
};

static struct sdcard_device sdcard_dev;

static void SDC_Uint32ToBytes(uint32_t val32, uint8_t *bytes)
{
#if SDC_HOST_MEMORY_IS_BIG_ENDIAN /* Big Endian. */
        bytes[3] = (uint8_t)(val32 >> 24);
        bytes[2] = (uint8_t)(val32 >> 16);
        bytes[1] = (uint8_t)(val32 >> 8);
        bytes[0] = (uint8_t)(val32);
#else /* Little Endian. */
        bytes[0] = (uint8_t)(val32 >> 24);
        bytes[1] = (uint8_t)(val32 >> 16);
        bytes[2] = (uint8_t)(val32 >> 8);
        bytes[3] = (uint8_t)(val32);
#endif
}

static uint8_t SDC_SpiCrc7(uint8_t crc, uint8_t input[], uint32_t len)
{
    /* pre-calculated table for CRC7 polynomial 0x89 */
    static const uint8_t crc_table[] =
    {
        0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36, 0x3F,
        0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77
    };

    uint8_t index;

    while (len) {
        index = ((crc>>3) & 0x0f) ^ ((*input)>>4);
        crc = (crc<<4) ^ crc_table[index];

        index = ((crc>>3) & 0x0f) ^ ((*input)&0x0f);
        crc = (crc<<4) ^ crc_table[index];

        input++;
        len--;
    }

    return (crc & 0x7f);
}

uint32_t sdcard_send_byte(uint8_t data)
{
    return sdcard_dev.ops.send(&data, 1, 0);
}

uint8_t sdcard_recv_byte(void)
{
    uint8_t data;
    sdcard_dev.ops.recv(&data, 1, 0);
    return data;
}

uint32_t sdcard_polling_byte(uint8_t data, uint32_t retry)
{
    uint8_t data_recv;
    while(retry)
    {
        data_recv = sdcard_recv_byte();
        if(data_recv == data)
        {
            return CH_OK;
        }
        retry--;
    }
    return CH_ERR;
}

static bool SDC_SendSpiCmd(uint8_t cmdNum, uint32_t param, uint8_t *resp)
{
    uint8_t buff[6];
    uint8_t index;
    uint32_t ret;
    SDCARD_TRACE("CMD %d...\r\n", cmdNum);

    sdcard_send_byte(0xFF);

    buff[0] = cmdNum | 0x40;
    SDC_Uint32ToBytes(param, &buff[1]);
    buff[5] = (SDC_SpiCrc7(0, buff, 5) << 1) | 0x01;

    sdcard_dev.ops.send(buff, 6, 0);

    index = 0;
    while (1)
    {
        sdcard_dev.ops.recv(resp, 1, 0);
        if (0xFF != *resp)
        {
            ret = CH_OK;
            break;
        }
        index++;
        if (index >= SDC_CMD_RESPONSE_TRY_LOOPS)
        {
            ret = CH_ERR;
            break;
        }
    }
    return ret;
}


uint32_t sdcard_get_size_mb()
{
    return sdcard_dev.blk_cnt/1024/2;
}

static uint32_t SDC_CsdCapacity(uint8_t *csd)
{
    uint32_t capacity, c_size, c_size_mult, read_bl_len;

    /* check CSD version */
    if (0 == (csd[0] & 0xC0))
    {
        read_bl_len = csd[5] & 0x0F;
        c_size = csd[6] & 0x03;
        c_size = (c_size << 8) | csd[7];
        c_size = (c_size << 2) | (csd[8] >> 6);
        c_size_mult = ((csd[9] & 0x03) << 1) | (csd[10] >> 7);
        capacity = (c_size + 1) * (1 << (c_size_mult + 2)) * (1 << (read_bl_len - 9));
    }
    else
    {
        c_size = csd[7] & 0x3F;
        c_size = (c_size << 8) | csd[8];
        c_size = (c_size << 8) | csd[9];
        capacity = (c_size + 1) << 10;
    }

    return capacity;
}

static int sdcard_probe(void)
{
    int i, timeout;
    uint8_t resp[4];
    uint32_t ret;
    uint8_t dummy;

    /* put cs to high */
    sdcard_dev.ops.send(&dummy, 1, 1);

    /* Reset the card with 74 clk ... */
    for (i=0; i<0xF; i++)
    {
        sdcard_send_byte(0xFF);
    }

    timeout = SDC_CMD_RESPONSE_TRY_LOOPS;
    while(timeout)
    {
        SDC_SendSpiCmd(CMD0, 0U, resp);
        if(*resp == 0x01)
        {
            SDCARD_TRACE("CMD0 OK\r\n");
            ret = CH_OK;
            break;
        }
        timeout--;
        if(timeout == 0)
        {
            return CH_ERR;
        }
    }

    /*
    * CMD8.
    * Get information from the card. If the particular voltage is supported.
    */
    timeout = SDC_CMD_RESPONSE_TRY_LOOPS;
    while (timeout)
    {
        SDC_SendSpiCmd(CMD8, 0x000001AAUL, resp);
        if (0x01U == *resp)
        {
            ret = CH_OK; /* Bingo. */
            SDCARD_TRACE("CMD8 OK\r\n");
            break;
        }
        timeout--;
        if(timeout == 0)
        {
            return CH_ERR;
        }
    }

    sdcard_dev.ops.recv(resp, 4, 0);
    if ( (resp[2] != 0x01U) || (resp[3] != 0xAAU) )
    {
        SDCARD_TRACE("CMD8 return data err\r\n");
        return CH_ERR;
    }

    /* CMD55 + CMD41. Prepare the card. */
    /* Check SDHC first, then SDv2. */
    timeout = SDC_CMD_RESPONSE_TRY_LOOPS;
    ret = CH_ERR;
    while (timeout)
    {
        SDC_SendSpiCmd(CMD55, 0U, resp);
        if (0x01U == *resp)
        {
            ret = CH_OK; /* Bingo. */
        }

        if(SDC_SendSpiCmd(CMD41, 0x40000000U, resp) == CH_OK)
        {
            if(ret == CH_OK && (*resp == 0x00))
            {
                SDCARD_TRACE("SDHC found!\r\n");
                sdcard_dev.type = kCard_SDHC;
                ret = CH_OK;
                break;
            }
            else
            {
                sdcard_dev.type = kCard_SDv2;
                SDCARD_TRACE("SDv2 found!\r\n");
                ret = CH_OK;
            }
        }
        timeout--;
    }

    /*
    * CMD58.
    * Check the OCR, get support voltage range and card type.
    */
    timeout = SDC_CMD_RESPONSE_TRY_LOOPS;
    ret = CH_ERR;
    while (timeout)
    {
        SDC_SendSpiCmd(CMD58, 0x000001AAUL, resp);
        if (0x00U == *resp)
        {
            ret = CH_OK; /* Bingo. */
            SDCARD_TRACE("CMD58 OK\r\n");
            break;
        }
        timeout--;
        if(timeout == 0)
        {
            return CH_ERR;
        }
    }

    sdcard_dev.ops.recv(resp, 4, 0); /* OCR */
//    SDCARD_TRACE("0x%2X\r\n", resp[0]);
//    SDCARD_TRACE("0x%2X\r\n", resp[1]);
//    SDCARD_TRACE("0x%2X\r\n", resp[2]);
//    SDCARD_TRACE("0x%2X\r\n", resp[3]);

    /*
    * CMD9
    * Get CSD and get card size. */
    timeout = SDC_CMD_RESPONSE_TRY_LOOPS;
    ret = CH_ERR;
    while (timeout)
    {
        SDC_SendSpiCmd(CMD9, 0UL, resp);
        if (0x0U == *resp) /* Right response. */
        {
            ret = CH_OK; /* Bingo. */
            SDCARD_TRACE("CMD9 OK\r\n");
            break;
        }
        timeout--;
        if(timeout == 0)
        {
            return CH_ERR;
        }
    }

    timeout = SDC_CMD_RESPONSE_TRY_LOOPS;
    ret = CH_ERR;
    while (timeout)
    {
        sdcard_dev.ops.recv(resp, 1, 0);
        if(*resp == 0xFE)
        {
            ret = CH_OK;
            SDCARD_TRACE("Wait for the start token OK\r\n");
            break;
        }
        timeout--;
    }

    /* Read CSD. */
    sdcard_dev.ops.recv(sdcard_dev.csd, 16, 0);
    sdcard_dev.blk_cnt = SDC_CsdCapacity(sdcard_dev.csd);
    SDCARD_TRACE("capacity:%dMB\r\n", sdcard_dev.blk_cnt/1024/2);

    return ret;
}

/* chip init */
int spi_sdcard_init(struct sdcard_init_t* init)
{
    uint32_t retry;
    memcpy(&sdcard_dev.ops, init, sizeof(struct sdcard_init_t));
    sdcard_dev.type = kCard_Unknow;

    retry = 10;
    while(retry)
    {
        if(sdcard_probe() == CH_OK)
        {
            return CH_OK;
        }
        retry--;
    }
    return CH_ERR;
}

uint32_t sdcard_read_single_block(uint32_t sector, uint8_t *buf)
{
    uint8_t r1;
    int ret;
    uint8_t tmp;

    if (sdcard_dev.type != kCard_SDHC)
    {
        sector <<= 9;
    }

    SDC_SendSpiCmd(CMD17, sector, &r1);
    (0x0 == r1)?(ret = CH_OK):(ret = CH_ERR);

    /* Wait for the token. */
    sdcard_polling_byte(0xFE, SDC_CMD_BUSY_TRY_LOOPS);

    /* Read data from card. */
    sdcard_dev.ops.recv(buf, 512, 0);

    /* Read two byte CRC. */
    sdcard_recv_byte();
    sdcard_dev.ops.recv(&tmp, 1, 1);

    return ret;
}

uint32_t sdcard_write_single_block(uint32_t sector, uint8_t *buf)
{
    uint8_t r1;
    int ret;
    uint32_t retry;
    if (sdcard_dev.type != kCard_SDHC)
    {
        sector <<= 9;
    }

    SDC_SendSpiCmd(CMD24, sector, &r1);
    (0x0 == r1)?(ret = CH_OK):(ret = CH_ERR);

    /* start token */
    sdcard_send_byte(0xFE);

    /* Write data from card. */
    sdcard_dev.ops.send(buf, 512, 0);

    /* Write two byte CRC. */
    sdcard_send_byte(0XFF);
    sdcard_send_byte(0XFF);

    retry = 10;
    ret = CH_ERR;
    while(retry)
    {
        sdcard_dev.ops.recv(&r1, 1, 0);
        if (0x05 == (r1 & 0x1F))
        {
            ret = CH_OK;
            break;
        }
        retry--;
    }

    /* 0xFF = Done, 0x0 = Busy. */
    sdcard_polling_byte(0xFF,  SDC_CMD_BUSY_TRY_LOOPS);

    return ret;
}


uint32_t sdcard_read_multi_block(uint32_t sector, uint8_t *buf, uint32_t cnt)
{
    uint32_t ret;
    uint8_t r1;
    uint8_t tmp;

    if (sdcard_dev.type != kCard_SDHC)
    {
        sector <<= 9;
    }

    SDC_SendSpiCmd(CMD18, sector, &r1);

    uint8_t *p = buf;
    while (cnt)
    {
        /* Wait for the token. */
        ret = sdcard_polling_byte(0xFE, SDC_CMD_BUSY_TRY_LOOPS);

        /* Read data from card. */
        sdcard_dev.ops.recv(p, 512, 0);

        /* Read two byte CRC. */
        sdcard_recv_byte();
        sdcard_dev.ops.recv(&tmp, 1, 1);

        p += 512;
        cnt--;
    }

    /* CMD12. STOP_TRANSMISSION */
    ret = SDC_SendSpiCmd(CMD12, 0U, &r1);

    return ret;
}


uint32_t sdcard_write_multi_block(uint32_t sector, uint8_t *buf, uint32_t cnt)
{
    uint32_t ret;
    uint8_t r1;
    uint32_t retry;

    if (sdcard_dev.type != kCard_SDHC)
    {
        sector <<= 9;
    }

    SDC_SendSpiCmd(CMD23, cnt, &r1);
    SDC_SendSpiCmd(CMD25, sector, &r1);

    /* Write two dummy bytes. */
    sdcard_send_byte(0XFF);
    sdcard_send_byte(0XFF);

    uint8_t *p = buf;
    while (cnt)
    {
        /* Write start token. */
        sdcard_send_byte(0XFC);

        /* Write data from card. */
        sdcard_dev.ops.send(p, 512, 0);

        /* Write two byte CRC. */
        sdcard_send_byte(0XFF);
        sdcard_send_byte(0XFF);

        retry = 10;
        while(retry)
        {
            sdcard_dev.ops.recv(&r1, 1, 0);
            if((r1 & 0x1F) == 0x05)
            {
                break;
            }
            retry--;
        }

        sdcard_polling_byte(0xFF,  SDC_CMD_BUSY_TRY_LOOPS);

        p += 512;
        cnt--;
    }

    /* stop tocken */
    sdcard_send_byte(0XFD);

    sdcard_send_byte(0XFF);
    sdcard_send_byte(0XFF);

    /* wait busy */
    ret = sdcard_polling_byte(0xFF, SDC_CMD_BUSY_TRY_LOOPS);

    return ret;
}



